package org.baojie.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.InetSocketAddress;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.ReentrantLock;

import org.baojie.server.initializer.ChannelInit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BaojieQueueServer {

    private static final Logger log = LoggerFactory.getLogger(BaojieQueueServer.class);
    private final ThreadLocalRandom random = ThreadLocalRandom.current();
    private final ReentrantLock mainLock = new ReentrantLock();
    private final boolean epollAvailable;
    private final QueueStatus status;
    private final int workNums;

    public BaojieQueueServer(int workNums) {
        this.workNums = workNums;
        this.status = new QueueStatus();
        this.epollAvailable = Epoll.isAvailable();
    }

    private final int randomPort() {
        int high = random.nextInt(10);
        if (high <= 0) {
            high = high + 1;
        }
        high = high * 1000;
        for (int i = 0; i < 3; i++) {
            int temp = random.nextInt(10);
            if (i == 0) {
                high = high + temp * 100;
            } else if (i == 1) {
                high = high + temp * 10;
            } else if (i == 2) {
                high = high + temp;
            }
        }
        return high;
    }

    public final boolean start() {
        final ReentrantLock lock = mainLock;
        lock.lock();
        try {
            if (!status.isStarted()) {
                startByLock();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

    private final ChannelFuture startByLock() {
        EventLoopGroup boss = eventLoop();
        EventLoopGroup worker = eventLoop();
        ServerBootstrap bootstrap = new ServerBootstrap();
        int randomPort = randomPort();
        boolean suc = group(boss, worker, bootstrap);
        if (suc) {
            channel(bootstrap);
            handler(bootstrap, randomPort);
            ChannelFuture future = asyncBind(bootstrap, randomPort);
            if (null == future) {
                shutdownGracefully(boss, worker);
            } else {
                future.addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) {
                        if (future.isSuccess()) {
                            status.setStarted(true);
                            status.setBoss(boss);
                            status.setWorker(worker);
                            status.setPort(randomPort);
                        } else {
                            Throwable err = future.cause();
                            log.error("bind occur error", err);
                            shutdownGracefully(boss, worker);
                        }
                    }
                });
            }
            return future;
        } else {
            shutdownGracefully(boss, worker);
            return null;
        }
    }

    private final ChannelFuture asyncBind(ServerBootstrap bootstrap, int port) {
        ChannelFuture future = null;
        try {
            future = bootstrap.bind(port);
        } catch (Throwable t) {

        }
        return future;
    }


    private final void shutdownGracefully(EventLoopGroup boss, EventLoopGroup worker) {
        try {
            if (null != boss) {
                boss.shutdownGracefully();
            }
        } finally {
            if (null != worker) {
                worker.shutdownGracefully();
            }
        }
    }

    private final EventLoopGroup eventLoop() {
        if (epollAvailable) {
            return new EpollEventLoopGroup(workNums);
        } else {
            return new NioEventLoopGroup(workNums);
        }
    }

    private final boolean group(EventLoopGroup boss, EventLoopGroup worker, ServerBootstrap bootstrap) {
        boolean suc = false;
        try {
            ServerBootstrap copy = bootstrap.group(boss, worker);
            if (null != copy) {
                suc = true;
            } else {
                suc = false;
            }
        } catch (Throwable t) {
            suc = false;
        }
        return suc;
    }

    private final void channel(ServerBootstrap bootstrap) {
        if (epollAvailable) {
            bootstrap.channel(EpollServerSocketChannel.class);
        } else {
            bootstrap.channel(NioServerSocketChannel.class);
        }
    }

    private final void handler(ServerBootstrap bootstrap, int port) {
        bootstrap.handler(new LoggingHandler(LogLevel.DEBUG));
        bootstrap.localAddress(new InetSocketAddress(port));
        bootstrap.childHandler(new ChannelInit());
        bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
        bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
    }

    private final class QueueStatus {

        private boolean started = false;
        private int port = -1;
        private EventLoopGroup boss;
        private EventLoopGroup worker;

        public QueueStatus() {

        }

        public boolean isStarted() {
            return started;
        }

        public void setStarted(boolean started) {
            this.started = started;
        }

        public int getPort() {
            return port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public EventLoopGroup getBoss() {
            return boss;
        }

        public void setBoss(EventLoopGroup boss) {
            this.boss = boss;
        }

        public EventLoopGroup getWorker() {
            return worker;
        }

        public void setWorker(EventLoopGroup worker) {
            this.worker = worker;
        }

        public void shutdownGracefully() {
            try {
                if (null != boss) {
                    boss.shutdownGracefully();
                }
            } finally {
                if (null != worker) {
                    worker.shutdownGracefully();
                }
            }
        }

    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8;
        }
        new BaojieQueueServer(port).start();
    }
}
